/*
 * Copyright (C) 2008-2012  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

/*
 * Prototypes
 */
static void
eval_root_ast(east_t *ast);

static void
eval_ast_stat(east_t *ast);

static void
eval_ast_decl(east_t *decl);

static void
eval_ast_declexpr(east_t *decl);

static void
eval_ast_call(east_t *call);

static void
eval_ast_goto(east_t *ast);

static void
eval_ast_sizeof(east_t *ast);

static void
update_symbol(east_t *ast);

static void
update_record(east_t *ast);

static void
update_vector(east_t *ast, etag_t *tag);

static eobject_t
validate_vector_offset(east_t *ast, etag_t *tag);

static void
validate_vector_offset_tag(east_t *ast, etag_t *tag);

/*
 * Initialization
 */
enamespace_t		*current_namespace;

/*
 * Implementation
 */
void
init_code(void)
{
    current_namespace = (enamespace_t *)root_function->namespace;
}

void
finish_code(void)
{
}

void
ecode(void)
{
    if ((root_function->ast->c.ast->l.ast = eparse())) {
	eval_root_ast(root_function->ast->c.ast->l.ast);
	ewrite_object(root_function->ast);
    }
}

void
eeval_ast(east_t *ast)
{
    east_t		*temp;

    switch (ast->token) {
	case tok_set:		case tok_andset:
	case tok_orset:		case tok_xorset:
	case tok_mul2set:	case tok_div2set:
	case tok_shlset:	case tok_shrset:
	case tok_addset:	case tok_subset:
	case tok_mulset:	case tok_divset:
	case tok_trunc2set:	case tok_remset:
	    eeval_ast(ast->l.ast);
	    eeval_ast(ast->r.ast);
	    break;
	case tok_vector:
	    eeval_ast(ast->l.ast);
	    eval_ast_stat(ast->r.ast);
	    /* validate element reference */
	    eeval_ast_tag(ast);
	    break;
	case tok_dot:
	    eeval_ast(ast->l.ast);
	    /* validate field reference */
	    eeval_ast_tag(ast);
	    break;
	case tok_andand:	case tok_oror:
	case tok_ne:		case tok_lt:
	case tok_le:		case tok_eq:
	case tok_ge:		case tok_gt:
	case tok_and:		case tok_or:
	case tok_xor:		case tok_mul2:
	case tok_div2:		case tok_shl:
	case tok_shr:		case tok_add:
	case tok_sub:		case tok_mul:
	case tok_div:		case tok_trunc2:
	case tok_rem:		case tok_complex:
	    eeval_ast(ast->l.ast);
	    eeval_ast(ast->r.ast);
	    efold(ast);
	    break;
	case tok_inc:		case tok_dec:
	case tok_postinc:	case tok_postdec:
	case tok_elemref:	case tok_fieldref:
	    eeval_ast(ast->l.ast);
	    break;
	case tok_com:		case tok_plus:
	case tok_neg:		case tok_not:
	    eeval_ast(ast->l.ast);
	    efold(ast);
	    break;
	case tok_atan2:		case tok_pow:
	case tok_hypot:		case tok_subtypeof:
	    eeval_ast(ast->l.ast);
	    eeval_ast(ast->r.ast);
	    break;
	case tok_sizeof:
	    eval_ast_sizeof(ast);
	    break;
	case tok_question:
	    eeval_ast(ast->t.ast);
	    eeval_ast(ast->l.ast);
	    eeval_ast(ast->r.ast);
	    efold(ast);
	    break;
	case tok_if:
	    eval_ast_stat(ast->t.ast);
	    eval_ast_stat(ast->l.ast);
	    eval_ast_stat(ast->r.ast);
	    efold(ast);
	    break;
	case tok_return:
	    if (ast->l.ast)
		eeval_ast(ast->l.ast);
	    break;
	case tok_switch:
	    eval_ast_stat(ast->t.ast);
	    eval_ast_stat(ast->c.ast);
	    break;
	case tok_for:
	    eval_ast_stat(ast->l.ast);
	    eval_ast_stat(ast->t.ast);
	    eval_ast_stat(ast->r.ast);
	    eval_ast_stat(ast->c.ast);
	    efold(ast);
	    break;
	case tok_do:		case tok_while:
	    eval_ast_stat(ast->c.ast);
	    eval_ast_stat(ast->t.ast);
	    efold(ast);
	    break;
	case tok_list:
	    temp = ast->l.ast;
	    eval_ast_stat(temp);
	    /*   Check inner fields for the sake of clean debug output. */
	    if (temp->next == null && temp->r.value == null &&
		temp->t.value == null &&
		(temp->c.value == null || etype(temp->c.value) == t_value))
		emove_ast_up_full(ast, temp);
	    break;
	case tok_call:
	    eval_ast_call(ast->l.ast);
	    eval_ast_stat(ast->r.ast);
	    break;
	case tok_stat:		case tok_code:
	case tok_data:
	    eval_ast_stat(ast->l.ast);
	    break;
	case tok_decl:
	    eeval_ast(ast->l.ast);
	    eval_ast_decl(ast->r.ast);
	    break;
	case tok_symbol:
	    update_symbol(ast);
	    break;
	case tok_goto:
	    eval_ast_goto(ast);
	    break;
	case tok_type:		case tok_number:	case tok_string:
	case tok_class:		case tok_label:		case tok_break:
	case tok_continue:	case tok_case:		case tok_default:
	    break;
	default:
#if DEBUG
	    eparse_warn(ast,
			"ecode: not handling %s", etoken_to_charp(ast->token));
#endif
	    break;
    }
}

etag_t *
eeval_ast_tag(east_t *ast)
{
    etag_t		*tag;
    erecord_t		*record;
    esymbol_t		*symbol;
    evector_t		*vector;

    switch (ast->token) {
	case tok_symbol:
	    update_symbol(ast);
	    symbol = ast->l.value;
	    return (symbol->tag);
	case tok_vector:
	    tag = eeval_ast_tag(ast->l.ast);
	    if (tag->type != tag_vector) {
		if (ast->l.ast->token == tok_symbol) {
		    symbol = ast->l.ast->l.value;
		    eparse_error(ast, "'%p' is not a vector %A",
				 symbol->name, ast);
		}
		eparse_error(ast, "not a vector reference %A", ast);
	    }
	    update_vector(ast, tag);
	    return (tag->base);
	case tok_dot:
	    tag = eeval_ast_tag(ast->l.ast);
	    if (tag->type != tag_class) {
		if (ast->l.ast->token == tok_symbol) {
		    symbol = ast->l.ast->l.value;
		    eparse_error(ast, "'%p' is not a class %A",
				 symbol->name, ast);
		}
		eparse_error(ast, "not a class reference %A", ast);
	    }
	    record = tag->name;
	    ast = ast->r.ast;
	    assert(ast->token == tok_symbol);
	    symbol = ast->l.value;
	    vector = symbol->name;
	    if ((symbol = eget_symbol(record, vector)) == null)
		eparse_error(ast, "'%p' has no field named '%p'",
			     record->name, vector);
	    ast->l.value = symbol;
	    update_record(ast);
	    return (symbol->tag);
	case tok_string:
	    /* FIXME should return a "sized" tag for constant strings */
	    return (string_tag);
	default:
	    eparse_error(ast, "not a type or symbol reference %A", ast);
    }
}

void
emove_left_ast_up(east_t *ast)
{
    assert(ast->r.ast == null);
    ast->r.ast = ast->l.ast;
    ast->token = ast->l.ast->token;
    ast->l.ast = ast->l.ast->l.ast;
    ast->r.ast->l.value = null;
    edel_object(&ast->r.value);
}

void
emove_ast_up_full(east_t *ast, east_t *move)
{
    assert(move == ast->l.ast || move == ast->r.ast ||
	   move == ast->t.ast || move == ast->c.ast);
    gc_push(move);
    ast->l.ast = move->l.ast;
    ast->r.ast = move->r.ast;
    ast->t.ast = move->t.ast;
    ast->c.ast = move->c.ast;
    ast->token = move->token;
    move->l.value = move->r.value = move->t.value = move->c.value = null;
    edel_object((eobject_t *)&move);
    gc_dec();
}

static void
eval_root_ast(east_t *ast)
{
    efunction_t		*function;
    efunction_t		*prev_function;
    enamespace_t	*prev_namespace;

    for (; ast; ast = ast->next) {
	if (ast->token == tok_function) {
	    prev_namespace = current_namespace;
	    prev_function = current_function;
	    function = ast->t.value;
	    current_function = function;
	    current_namespace = function->namespace;
	    eeval_ast(ast->l.ast);
	    eval_ast_declexpr(ast->r.ast->r.ast);
	    eeval_ast(ast->c.ast);

	    essa(function->ast->c.ast);
	    econst(function->ast->c.ast);
	    etype_ast(function->ast->c.ast);

	    current_function = prev_function;
	    current_namespace = prev_namespace;
	}
	else
	    eeval_ast(ast);
    }

    assert(current_function == root_function);
    essa(root_function->ast->c.ast);
    econst(root_function->ast->c.ast);
    etype_ast(root_function->ast->c.ast);
}

static void
eval_ast_stat(east_t *ast)
{
    for (; ast; ast = ast->next)
	eeval_ast(ast);
}

static void
eval_ast_decl(east_t *decl)
{
    east_t		*ast;
    east_t		*list;
    esymbol_t		*symbol;
    evector_t		*vector;

    for (list = decl; list; list = list->next) {
	ast = list;
	switch (ast->token) {
	    case tok_symbol:
		break;
	    case tok_set:	case tok_vector:	case tok_proto:
		eeval_ast(ast->r.ast);
		for (ast = ast->l.ast;
		     ast->token != tok_symbol; ast = ast->l.ast)
		    ;
		break;
	    default:
		abort();
	}

	/* make symbol bound */
	symbol = ast->l.value;
	vector = symbol->name;
	symbol = eget_symbol(current_namespace, vector);
	assert(symbol);
	ast->l.value = symbol;
	symbol->bound = true;

	/* remember symbol is referenced */
	update_symbol(ast);
    }
}

static void
eval_ast_declexpr(east_t *decl)
{
    east_t		*list;

    for (list = decl; list; list = list->next) {
	assert(list->token == tok_declexpr);
	eval_ast_decl(list->r.ast);
    }
}

static void
eval_ast_call(east_t *call)
{
    if (call->token != tok_symbol) {
	assert(call->token == tok_set || call->token == tok_vector);
	eeval_ast(call);
    }
}

static void
eval_ast_goto(east_t *ast)
{
    eentry_t		*entry;
    esymbol_t		*symbol;

    symbol = ast->l.ast->l.value;
    entry = eget_hash(current_function->label_table, symbol->name);
    if (entry == null)
	eparse_error(ast, "undefined label '%p'", symbol->name);
    /*   Use ast->c.ast to match other break, case, continue and default. */
    ast->c.ast = entry->value;
}

static void
eval_ast_sizeof(east_t *ast)
{
    etag_t		*tag;
    eword_t		 size;
    evector_t		*vector;

    eeval_ast(ast->l.ast);
    switch (ast->l.ast->token) {
	case tok_type:
	    tag = ast->l.ast->l.value;
	eval_type:
	    if (tag->type == tag_vector) {
		vector = tag->name;
		size = *vector->v.w;
	    }
	    else
		size = 0;
	    break;
	case tok_number:
	    size = 0;
	    break;
	case tok_string:
	    vector = ast->l.ast->l.value;
	    size = vector->length;
	    break;
	default:
	    tag = eeval_ast_tag(ast->l.ast);
	    goto eval_type;
    }
    edel_object(&ast->l.value);
    ast->token = tok_number;
    enew_word(&ast->l.value, size);
}

static void
update_symbol(east_t *ast)
{
    evalue_t		*value;
    esymbol_t		*symbol;
    evector_t		*vector;
    eobject_t		*pointer;

    symbol = ast->l.value;
    if (!symbol->bound) {
	vector = symbol->name;
	symbol = eget_bound_symbol(vector);
	if (symbol == null)
	    eparse_error(ast, "unbound symbol '%p'", vector);
	/*   This also happens to error out if using a now shadowed variable. */
	if (!symbol->bound)
	    eparse_error(ast, "symbol '%p' used before definition", vector);
	ast->l.value = symbol;
    }

    /*  Remember symbol is referenced and create a unique identifier for it. */
    value = (evalue_t *)eget_hash(current_function->value->hash, symbol);
    if (value == null) {
	vector = current_function->value_vector;
	gc_ref(pointer);
	enew_value(pointer, symbol, vector->offset);
	eput_hash(current_function->value->hash, *pointer);
	gc_pop(value);
	if (vector->offset >= vector->length)
	    erenew_vector(vector, vector->length + 8);
	vector->v.ptr[vector->offset++] = value;
    }
    ast->c.value = value;
}

static void
update_record(east_t *ast)
{
    evalue_t		*base;
    evalue_t		*value;
    esymbol_t		*symbol;
    evector_t		*vector;
    eobject_t		*pointer;
    enamespace_t	*namespace;

    assert(ast->token == tok_symbol);
    symbol = ast->l.value;
    /*   Use parent symbol as common identifier in derived classes. */
    if (symbol->parent)
	symbol = symbol->parent;

    namespace = symbol->namespace;
    if (current_function->alias_table == null) {
	base = null;
	enew_hash((eobject_t *)&current_function->alias_table, 8);
    }
    else
	base = (evalue_t *)eget_hash(current_function->alias_table,
				     namespace->name);

    if (base == null) {
	value = null;
	gc_ref(pointer);
	/*  The record itself is only a placeholder for the aliased fields. */
	enew_value(pointer, namespace->name, -1);
	eput_hash(current_function->alias_table, *pointer);
	gc_pop(base);
	enew_hash((eobject_t *)&base->hash, 8);
    }
    else
	value = (evalue_t *)eget_hash(base->hash, symbol);
    if (value == null) {
	vector = current_function->value_vector;
	gc_ref(pointer);
	enew_value(pointer, symbol, vector->offset);
	eput_hash(base->hash, *pointer);
	gc_pop(value);
	if (vector->offset >= vector->length)
	    erenew_vector(vector, vector->length + 8);
	vector->v.ptr[vector->offset++] = value;
    }
    ast->c.value = value;
}

static void
update_vector(east_t *ast, etag_t *tag)
{
    evalue_t		*base;
    eword_t		*word;
    evalue_t		*value;
    eobject_t		 object;
    evector_t		*vector;
    eobject_t		*pointer;

    tag = etag_vector_alias(tag);

    assert(ast->token == tok_vector);
    object = validate_vector_offset(ast->r.ast, tag);

    if (current_function->alias_table == null) {
	base = null;
	enew_hash((eobject_t *)&current_function->alias_table, 8);
    }
    else
	base = (evalue_t *)eget_hash(current_function->alias_table, tag);

    vector = current_function->value_vector;
    if (base == null) {
	value = null;
	gc_ref(pointer);
	enew_value(pointer, tag, vector->offset);
	eput_hash(current_function->alias_table, *pointer);
	gc_pop(base);
	enew_hash((eobject_t *)&base->hash, 8);
	if (vector->offset >= vector->length)
	    erenew_vector(vector, vector->length + 8);
	vector->v.ptr[vector->offset++] = base;
    }
    else
	value = (evalue_t *)eget_hash(base->hash, object);
    if (object == tag)
	ast->c.value = base;
    else {
	if (value == null) {
	    /*   Make a copy object as the ast node may be deleted
	     * later, what would cause a dangling pointer due to ast
	     * contents being cached and assuming no shared pointers. */
	    word = object;
	    gc_ref(pointer);
	    eget_word(pointer);
	    object = *pointer;
	    *(eword_t *)object = *word;
	    gc_ref(pointer);
	    enew_value(pointer, object, vector->offset);
	    eput_hash(base->hash, *pointer);
	    gc_pop(value);
	    gc_dec();
	    if (vector->offset >= vector->length)
		erenew_vector(vector, vector->length + 8);
	    vector->v.ptr[vector->offset++] = value;
	    value->parent = base;
	}
	if (base->alias == null)
	    enew_vector((eobject_t *)&base->alias, t_void, 8);
	else if (base->alias->offset >= base->alias->length)
	    erenew_vector(base->alias, base->alias->length + 8);
	base->alias->v.ptr[base->alias->offset++] = value;
	ast->c.value = value;
    }
}

static eobject_t
validate_vector_offset(east_t *ast, etag_t *tag)
{
    eobject_t		 object;
    esymbol_t		*symbol;

    switch (ast->token) {
	case tok_number:
	    object = ast->l.value;
	    if (etype(object) != t_word)
		eparse_error(ast, "offset is not an integer %A", ast);
	    break;
	case tok_symbol:
	    symbol = ast->l.value;
	    validate_vector_offset_tag(ast, symbol->tag);
	    object = tag;
	    break;
	default:
#if 0	/* FIXME Need to evaluate expressions combining types, but complex
	 * expressions most times should reduce to auto_tag...  Either way,
	 * should try hard to prevent runtime errors, and if reducing to
	 * auto_tag, print a warning message (or error if surely an invalid
	 * offset type). */
	    validate_vector_offset_tag(ast, eeval_ast_tag(ast));
#endif
	    object = tag;
	    break;
    }

    return (object);
}

static void
validate_vector_offset_tag(east_t *ast, etag_t *tag)
{
    ebasic_t		*basic;

    if (tag->type == tag_basic) {
	basic = tag->name;
	switch (basic->type) {
	    case t_int8:	case t_uint8:
	    case t_int16:	case t_uint16:
	    case t_int32:	case t_uint32:
	    case t_int64:	case t_uint64:
		break;
	    default:
		eparse_error(ast, "offset is not an integer %A", ast);
	}
    }
    else if (tag->type != tag_auto)
	eparse_error(ast, "offset is not an integer %A", ast);
}
